package org.hsqldb.index;

import java.io.IOException;

import org.hsqldb.Row;
import org.hsqldb.RowBTree;
import org.hsqldb.RowBTreeDisk;
import org.hsqldb.Table;
import org.hsqldb.TableBase;
import org.hsqldb.persist.PersistentStore;
import org.hsqldb.persist.RowStoreBTreeDisk;
import org.hsqldb.rowio.RowInputInterface;
import org.hsqldb.rowio.RowOutputBinary;
import org.hsqldb.rowio.RowOutputBinaryEncode;
import org.hsqldb.rowio.RowOutputInterface;
import org.hsqldb.types.*;;
public class NodeBTreeBranchDisk extends NodeBTreeBranch implements NodeBTreeDisk,IndexNode{
	
	TableBase table;
	protected int indexPosition = NO_POS;
	protected int selfNodeId = NO_POS;
	protected int parentNodeId = NO_POS;
	protected int nextLeafId = NO_POS;
	protected int prevLeafId = NO_POS;
	
	protected int[] childNodeIds;
	protected int keyColNumber;
	protected int[] keyColIndex;
	
	int     storageSize;
    int     keepCount;
    boolean isInMemory;
    int     accessCount = 0;
	
    /**
     *  Flag indicating unwritten data.
     */
    private boolean hasDataChanged;

    /**
     *  Flag indicating Node data has changed.
     */
    private boolean hasNodesChanged;
    
	public NodeBTree getChild(PersistentStore store, int position) {
		if (position < 0 || position > TREE_NODE_LENGTH)
			return null;
		if(childNodeIds[position] == NO_POS)
			return null;
		if(childNodes[position] != null)
			return childNodes[position];
		if(isLeaf)
			return (NodeBTree) ((RowBTreeDisk)store.get(childNodeIds[position])).getNode(indexPosition);
		return childNodes[position] = (NodeBTree) store.get(childNodeIds[position]);
	}
	
	public NodeBTree getNextLeaf(PersistentStore store){
		if (!isLeaf)
			return null;
		if(nextLeafId == NO_POS)
			return null;
		if(nextLeaf != null)
			return nextLeaf;
		return nextLeaf = (NodeBTree) store.get(nextLeafId);
	}
	
	public void setNextLeaf(PersistentStore store,NodeBTree node){
		if(node == null){
			nextLeaf = node;
			nextLeafId = NO_POS;
		}
		else{
			nextLeaf = node;
			nextLeafId = node.getPos();
		}
		setNodeChanged();
	}
	
	public NodeBTree getPreLeaf(PersistentStore store){
		if (!isLeaf)
			return null;
		if(prevLeafId == NO_POS)
			return null;
		if(preLeaf != null)
			return preLeaf;
		return preLeaf = (NodeBTree) store.get(prevLeafId);
	}
	
	@Override
    public int getPos(){
    	return this.selfNodeId;
    }
	@Override
	public void setPos(int p){
		selfNodeId = p;
	}
	
	public void setPreLeaf(PersistentStore store,NodeBTree node){
		if(node == null){
			preLeaf = node;
			prevLeafId = NO_POS;
		}
		else{
			preLeaf = node;
			prevLeafId = node.getPos();
		}
		setNodeChanged();
	}
	
	public NodeBTree getParent(PersistentStore store) {
		if(parentNodeId == NO_POS)
			return null;
		if(nodeParent != null)
			return nodeParent;
		return nodeParent = (NodeBTree) store.get(parentNodeId);
	}
	
	public void setChild(PersistentStore store,NodeBTree node,int position){
		childNodes[position] = node;
		setNodeChanged();
		if(node != null){
			childNodeIds[position] = node.getPos();
			if(isLeaf){
				while(node != null){
					node.setParent(store,this,position);
					node = ((NodeBTreeData)node).getNext(store);
				}
			}
			else{
				node.setParent(store,this,position);
			}
		}
		else {
			childNodeIds[position] = NO_POS;
		}
	}
	@Override
	public void setParent(PersistentStore store,NodeBTree node, int pos){
    	nodeParent = node;
    	positionInParent = pos;
    	if(node == null)
    		parentNodeId = NO_POS;
    	else {
			parentNodeId = node.getPos();
		}
    	setNodeChanged();
    }
	
	public int getKeyColNumber(){
		return keyColNumber;
	}
	public int getKeyColIndex(int i){
		return keyColIndex[i];
	}
	
	public NodeBTreeBranchDisk(TableBase t,int indexId,int[] colIndexes){
		
		super();
		
		table = t;
		indexPosition = indexId;
		keyColIndex = colIndexes;
		keyColNumber = keyColIndex.length;
		
		childNodeIds = new int[TREE_NODE_LENGTH + 1];
		for (int i = 0; i < childNodeIds.length; i++) {
			childNodeIds[i] = NO_POS;
		}
		
		hasNodesChanged = true;
		hasDataChanged = true;
	}
	public NodeBTreeBranchDisk(TableBase t,RowInputInterface input) throws IOException{
		
		super();
		table = t;
		selfNodeId = input.getPos();
		storageSize = input.getSize();
		
		
		indexPosition = input.readInt();
		keyNumber = input.readInt();
		positionInParent = input.readInt();
		isLeaf = (input.readInt() == 1);
		keyColNumber = input.readInt();
		
		keyColIndex = new int[keyColNumber];
		childNodeIds = new int[TREE_NODE_LENGTH + 1];
		for(int i = 0;i < keyColNumber;i++){
			keyColIndex[i] = input.readInt();
		}
		for(int i = 0;i < position.length;i++){
			position[i] = NO_POS;
		}
		for(int i = 0;i < position.length;i++){
			position[i] = input.readInt();
		}
		
		for(int i = 0;i < childNodeIds.length;i++){
			childNodeIds[i] = NO_POS;
		}
		for(int i = 0;i < childNodeIds.length;i++){
			childNodeIds[i] = input.readInt();
		}
		
		parentNodeId = input.readInt();
		nextLeafId = input.readInt();
		prevLeafId = input.readInt();
		
		Type[] types = table.getColumnTypes();
		Type[] mappedTypes = new Type[keyColNumber];
		for (int i = 0; i < mappedTypes.length; i++) {
			mappedTypes[i] = types[keyColIndex[i]];
		}
		for (int i = 0; i < keyNumber; i++) {
			keys[i] = input.readData(mappedTypes);
		}
	}
	@Override
	public int getRealSize(RowOutputInterface outputInterface){
		int keysSize = ((RowOutputBinaryEncode)outputInterface).getIndexNodeSize(this);
		
		int sizeOfInt = 4;//Integer.4;
		int fieldSize = 
				sizeOfInt * 5 
				+ sizeOfInt * keyColNumber 
				+ position.length * sizeOfInt
				+ childNodeIds.length * sizeOfInt
				+ 3 * sizeOfInt;
		return fieldSize + keysSize;
	}
	
	@Override
	public void write(RowOutputInterface out){
		out.writeSize(storageSize);

		out.writeInt(NODE_TYPE_BRANCH);
		out.writeInt(indexPosition);
		out.writeInt(keyNumber);
		out.writeInt(positionInParent);
		out.writeInt(isLeaf?0x1:0x0);
		out.writeInt(keyColNumber);
		
		
		for (int i = 0; i < keyColIndex.length; i++) {
			out.writeInt(keyColIndex[i]);
		}
		
		for(int i = 0;i < position.length;i++){
			out.writeInt(position[i]);
		}
		for(int i = 0;i < childNodeIds.length;i++){
			out.writeInt(childNodeIds[i]);
		}
		
		out.writeInt(parentNodeId);
		out.writeInt(nextLeafId);
		out.writeInt(prevLeafId);
		
		Type[] types = table.getColumnTypes();
		for (int i = 0; i < keyNumber; i++) {
			Object[] keyObjects = (Object[]) keys[i];
			for (int j = 0; j < keyColIndex.length; j++) {
				((RowOutputBinary)out).writeData(types[keyColIndex[j]], keyObjects[j]);
			}
		}
		hasDataChanged = hasNodesChanged = false;
	}

	@Override
	public Object[] getKeys() {
		return keys;
	}

	@Override
	public int[] getKeyColIndex() {
		return keyColIndex;
	}

	
	@Override
	public void setStorageSize(int s){
		storageSize = s;
	}
	
	@Override
	public int getStorageSize(){
		return storageSize;
	}
	
	@Override
	public int getAccessCount(){
		return accessCount;
	}
	
	public void visit(PersistentStore store){
		accessCount++;
	}
	@Override
	public void setNodeChanged(){
		hasNodesChanged = true;
	}
	@Override
	public NodeBTreeBranch split(PersistentStore store,int pos){
		if (pos < 0 || pos >= keyNumber){
			return null;
		}
		NodeBTreeBranchDisk newNode = new NodeBTreeBranchDisk(table,indexPosition,keyColIndex);
		store.add(newNode);
		this.setNodeChanged();
		newNode.setIsLeaf(isLeaf);
		for (int i = 0; i <= keyNumber - pos - 1; i++){
			if (isLeaf) {
				newNode.appendDataForLeaf(store,(Object[])keys[i+pos], (NodeBTreeData)getChild(store, i+pos));
			}
			else {
				newNode.appendKeyAndChildForNonLeaf(store,(Object[])keys[i+pos], (NodeBTreeBranch)getChild(store, i+pos));
			}
			keys[i+pos] = null;
			this.setChild(store,null, i + pos);//[i+pos] = null; 
		}
		if(!isLeaf){
			newNode.setChild(store, this.getChild(store, keyNumber), newNode.getChildNumber());
		}
		keyNumber = pos;
		return newNode;
	}
	
	
	/**
     * Sets flag for row data change.
     */
    public synchronized void setChanged(boolean changed) {
        hasDataChanged = changed;
    }

    /**
     * Returns true if Node data has changed.
     *
     * @return boolean
     */
    public synchronized boolean hasChanged() {
        return hasNodesChanged || hasDataChanged;
    }
	@Override
	public Table getTable() {
		// TODO Auto-generated method stub
		return (Table) table;
	}
}
